package org.fujene.communicate;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import java.util.HashMap;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.fujene.communicate.deleter.DeleterPackage;
import org.fujene.index.IndexingPackage;
import org.fujene.search.SearchPackage;
import org.fujene.search.SearchResult;
import org.fujene.search.SearchResultPackage;
import org.fujene.structure.ContentInfo;
import org.fujene.structure.SearchRangeTuple;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class MainHTMLServer implements Runnable{
	private MainService mains;
	private ContentInfo contentInfo;

	private enum Status{
		OK(200), BAD_REQUEST(400), NOT_FOUND(404), METHOD_NOT_FOUND(405), INTERNAL_SERVER_ERROR(500), NOT_IMPLEMENTED(
			501);
		int value;

		private Status(int val){
			this.value = val;
		}
	}

	private enum Request{
		GET, HEAD, POST;
	}

	private ServerSocket srvsocket;

	/** Temporarily sets port to 8080. */
	public MainHTMLServer(MainService mains, ContentInfo contentInfo){
		this.mains = mains;
		this.contentInfo = contentInfo;

		try{
			srvsocket = new ServerSocket();
			srvsocket.setReuseAddress(true);
			srvsocket.bind(new InetSocketAddress(8080));
		}catch(IOException e){
			e.printStackTrace();
		}
	}

	private class HTTPHeaderHandler{
		private InputStream stream;
		private Request req;
		private int status;
		private HashMap<String, String> infos;
		private Socket sock;

		public HTTPHeaderHandler(Socket sock) throws IOException{
			this.sock = sock;
			stream = sock.getInputStream();
			byte[] header = new byte[4096];
			stream.read(header);

			int i = 3;
			for(; (header[i - 3] != '\r' || header[i - 2] != '\n' || header[i - 1] != '\r' || header[i] == '\n')
				&& header[i] != 0; i++);
			String headerStr = new String(Arrays.copyOfRange(header, 0, i));
			String[] headers = headerStr.split(":\r\n");
			if(headers.length < 1){
				status = Status.BAD_REQUEST.value;
				return;
			}
			String[] verInfo = headers[0].split(" ");
			if(verInfo[0] == "GET") req = Request.GET;
			else if(verInfo[0] == "POST") req = Request.POST;
			else if(verInfo[0] == "HEAD") req = Request.HEAD;
			else{
				status = Status.BAD_REQUEST.value;
				return;
			}

			infos.put("Path", verInfo[1]);
			infos.put("Version", verInfo[2]);

			for(i = 1; (i + 1) < headers.length; i += 2){
				infos.put(headers[i], headers[i + 1]);
			}

			status = Status.OK.value;
		}

		public void responseIndex(){
			try{
				if(status == Status.OK.value) responseIndexOK();
				else if(status == Status.BAD_REQUEST.value) responseIndexBadReq();
			}catch(IOException e){
				e.printStackTrace();
			}
		}

		public void responseSearch(SearchResultPackage pkg){
			try{
				if(status == Status.OK.value) responseSearchOK(pkg);
				else if(status == Status.BAD_REQUEST.value) responseIndexBadReq();
			}catch(IOException e){
				e.printStackTrace();
			}catch(ParserConfigurationException e){
				e.printStackTrace();
			}catch(TransformerException e){
				e.printStackTrace();
			}
		}

		private void responseSearchOK(SearchResultPackage pkg) throws IOException,
			ParserConfigurationException, TransformerException{
			OutputStream stream = sock.getOutputStream();
			PrintWriter writer = null;

			DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = docFactory.newDocumentBuilder();

			DOMImplementation domImpl = builder.getDOMImplementation();

			Document rootDoc = domImpl.createDocument("", "result", null);
			Element rootElem = rootDoc.getDocumentElement();

			int counter = pkg.from() + 1;
			for(SearchResult searchResult: pkg){
				Element docElem = rootDoc.createElement("document");

				// <index>
				Element indexElem = rootDoc.createElement("index");
				indexElem.appendChild(rootDoc.createTextNode(Integer.toString(counter)));
				docElem.appendChild(indexElem);

				for(int i = 0; i < searchResult.contentSize(); i++){
					// <field> content
					Element fieldElem = rootDoc.createElement("field");
					fieldElem.appendChild(rootDoc.createTextNode(searchResult.content(i)));
					Attr fieldAttr = rootDoc.createAttribute("name");
					fieldAttr.setValue(contentInfo.contentName(i));
					fieldElem.setAttributeNode(fieldAttr);
					docElem.appendChild(fieldElem);
				}

				for(int i = 0; i < searchResult.appendixSize(); i++){
					// <field> appendix
					Element fieldElem = rootDoc.createElement("field");
					fieldElem.appendChild(rootDoc.createTextNode(searchResult.appendix(i)));
					Attr fieldAttr = rootDoc.createAttribute("name");
					fieldAttr.setValue(contentInfo.appendixName(i));
					fieldElem.setAttributeNode(fieldAttr);
					docElem.appendChild(fieldElem);
				}

				rootElem.appendChild(docElem);
				counter++;
			}

			TransformerFactory transFactory = TransformerFactory.newInstance();
			Transformer transformer = transFactory.newTransformer();

			DOMSource domSource = new DOMSource(rootDoc);
			ByteArrayOutputStream tempStream = new ByteArrayOutputStream();
			StreamResult streamResult = new StreamResult(tempStream);
			transformer.transform(domSource, streamResult);

			try{
				writer = new PrintWriter(stream);
				writer.print("HTTP/1.1 200 OK\r\n");
				writer.print("Connection: Close\r\n");
				writer.print("Content-Length: ");
				writer.print(tempStream.size());
				writer.print("\r\n");
				writer.print("Content-Type: text/xml\r\n");
				// write out XML data
				writer.print(tempStream.toString("UTF-8"));
				writer.print("\r\n\r\n");
			}finally{
				if(writer != null) writer.close();
			}
		}

		private void responseIndexOK() throws IOException{
			OutputStream stream = sock.getOutputStream();
			PrintWriter writer = null;
			try{
				writer = new PrintWriter(stream);
				writer.print("HTTP/1.1 200 OK\r\n");
				writer.print("Connection: Close\r\n");
				writer.print("Content-Length: 0");
				writer.print("\r\n\r\n");
			}finally{
				if(writer != null) writer.close();
			}
		}

		private void responseIndexBadReq() throws IOException{
			String errorHTML =
				"<html><head><title>ORBIS API system</title></head><body><h1>400 Bad request</h1></body></html>";
			OutputStream stream = sock.getOutputStream();
			PrintWriter writer = null;
			try{
				writer = new PrintWriter(stream);
				writer.print("HTTP/1.1 400 Bad Request\r\n");
				writer.print("Connection: Close\r\n");
				writer.print("Content-Length: " + errorHTML.length());
				writer.print("Content-Type: text/html\r\n");
				writer.print(errorHTML);
				writer.print("\r\n");
				writer.print("\r\n\r\n");
			}finally{
				if(writer != null) writer.close();
			}
		}

		public Request request(){
			return req;
		}

		public String getHeader(String type){
			return infos.get(type);
		}

		public void setStatus(Status stvalue){
			this.status = stvalue.value;
		}
	}

	@Override
	public void run(){
		try{
			for(Socket sock = srvsocket.accept(); sock != null; sock = srvsocket.accept()){
				try{
					HTTPHeaderHandler handler = new HTTPHeaderHandler(sock);

					if(handler.request() == Request.GET){
						if(handler.getHeader("Path").startsWith("/index")){
							// Indexing
							IndexingPackage pkg = new IndexingPackage(contentInfo);
							String[] fields =
								handler.getHeader("Path").substring("/index?".length()).split("&");
							String[] valueSet;
							int index;
							for(int i = 0; i < fields.length; i++){
								valueSet = fields[i].split("=");
								if(valueSet.length < 2){
									handler.setStatus(Status.BAD_REQUEST);
									handler.responseIndex();
									continue;
								}
								if((index = contentInfo.contentIndex(valueSet[0])) != -1) pkg.setContent(
									valueSet[1], index);
								else if((index = contentInfo.appendixIndex(valueSet[0])) != -1) pkg
									.setAppendix(valueSet[1], index);
								else if((index = contentInfo.binaryFlagIndex(valueSet[0])) != -1){
									if(valueSet[1] == "false") pkg.setBinaryFlag(false, index);
									else if(valueSet[1] == "true") pkg.setBinaryFlag(true, index);
								}else if((index = contentInfo.multipleFlagIndex(valueSet[0])) != -1){
									pkg.setMultipleFlag(Integer.parseInt(valueSet[1]), index);
								}
							}

							mains.setNewIndex(pkg);

							handler.responseIndex();
						}else if(handler.getHeader("Path").startsWith("/delete")){
							String[] fields =
								handler.getHeader("Path").substring("/delete?".length()).split("&");
							String[] valueSet = fields[0].split("=");
							if(valueSet[0] != "key"){
								handler.setStatus(Status.BAD_REQUEST);
								handler.responseIndex();
								continue;
							}
							if(mains.getDeleteKeyType() == DeleterPackage.TYPE_ID) mains.setDelete(Long
								.parseLong(valueSet[1]));
							else if(mains.getDeleteKeyType() == DeleterPackage.TYPE_CONTENT)
								mains.setDelete(valueSet[1]);
							if(mains.getDeleteKeyType() == DeleterPackage.TYPE_FLAG)
								mains.setDelete(Integer.parseInt(valueSet[1]));

							handler.responseIndex();
						}else if(handler.getHeader("Path").startsWith("/search")){
							String[] fields =
								handler.getHeader("Path").substring("/search?".length()).split("&");
							String[] valueSet;
							byte[] targetb = null;
							String query = null;
							int index;
							int from = 0, length = 10;
							byte[] binaryFlags = new byte[contentInfo.binaryFlagNum()];
							byte[] rangeTypes = new byte[contentInfo.multipleFlagNum()];
							int[] rangeFroms = new int[contentInfo.multipleFlagNum()];
							int[] rangeTos = new int[contentInfo.multipleFlagNum()];

							for(int i = 0; i < binaryFlags.length; i++)
								binaryFlags[i] = -1;
							for(int i = 0; i < rangeTypes.length; i++){
								rangeTypes[i] = SearchRangeTuple.RANGE_DONTCARE;
								rangeFroms[i] = Integer.MIN_VALUE;
								rangeTos[i] = Integer.MAX_VALUE;
							}

							for(int i = 0; i < fields.length; i++){
								valueSet = fields[i].split("=");
								if(valueSet.length < 2){
									handler.setStatus(Status.BAD_REQUEST);
									handler.responseIndex();
									continue;
								}
								if(valueSet[0] == "Query") query = valueSet[1];
								else if(valueSet[0] == "From") from = Integer.valueOf(valueSet[1]);
								else if(valueSet[0] == "Length") length = Integer.valueOf(valueSet[1]);
								else if(valueSet[0] == "Target"){
									String[] targets = valueSet[1].split(",");
									targetb =
										new byte[(contentInfo.contentsNum() % 8 == 0? contentInfo
											.contentsNum() / 8: contentInfo.contentsNum() / 8 + 1)];
									for(int j = 1; j < targets.length; j++){
										if((index = contentInfo.contentIndex(targets[j])) != -1){
											int bindex = index / 8;
											int bbit = index % 8;
											targetb[bindex] += (1 << bbit);
										}
									}
								}else if((index = contentInfo.binaryFlagIndex(valueSet[0])) != -1){
									if(valueSet[1] == "true") binaryFlags[i] = 1;
									else if(valueSet[1] == "false") binaryFlags[i] = 0;
								}else if((index = contentInfo.multipleFlagIndex(valueSet[0])) != -1){
									if(!valueSet[1].endsWith("-")){
										String[] ranges = valueSet[1].split("<");
										if(ranges.length < 2){
											handler.setStatus(Status.BAD_REQUEST);
											handler.responseIndex();
											continue;
										}
										rangeTypes[index] = SearchRangeTuple.RANGE_RANGE;
										rangeFroms[index] = Integer.parseInt(ranges[0]);
										if(ranges[1].startsWith(">")){
											rangeTypes[index] = SearchRangeTuple.RANGE_NOTRANGE;
											rangeTos[index] = Integer.parseInt(ranges[1].substring(1));
										}else{
											rangeTos[index] = Integer.parseInt(ranges[1]);
										}
									}else{
										if(valueSet[1].endsWith("--")) rangeTypes[index] =
											SearchRangeTuple.RANGE_EQUAL;
										else if(valueSet[1].endsWith("!-")) rangeTypes[index] =
											SearchRangeTuple.RANGE_NOTEQUAL;
										else if(valueSet[1].endsWith("<-")) rangeTypes[index] =
											SearchRangeTuple.RANGE_GREATER;
										else if(valueSet[1].endsWith(">-"))
											rangeTypes[index] = SearchRangeTuple.RANGE_LESSOREQUAL;
										rangeFroms[index] =
											Integer.parseInt(valueSet[1].substring(0,
												valueSet[1].length() - 2));
									}
								}
							}
							SearchPackage pkg =
								SearchPackage.createPackage(query, from, length, targetb, false,
									contentInfo.binaryFlagNum(), contentInfo.multipleFlagNum());
							for(int i = 0; i < binaryFlags.length; i++)
								pkg.setBinaryFlag(i, binaryFlags[i]);
							for(int i = 0; i < rangeTypes.length; i++)
								pkg.setMultipleFlag(i, rangeTypes[i], rangeFroms[i], rangeTos[i]);

							SearchResultPackage result = mains.setNewSearch(pkg);
							handler.responseSearch(result);
						}

					}
				}catch(Exception e){}finally{
					sock.close();
				}
			}
		}catch(IOException e){
			e.printStackTrace();
		}
	}
}
